// 对象转换成 查询字符串
function querystringify(obj) {
  let str = ''
  for (let k in obj) {
    // str += 'name' + '=' + 'jack' + '&'
    // str += 'age' + '=' + '18' + '&'
    str += k + '=' + obj[k] + '&'
  }

  // str === 'name=jack&age=18&'
  return str.slice(0, -1)
}

// ajax 请求
/*
  1. 验证参数内容
    1-1. 验证 options.url
      => 必填, 只要没有填写, 就不能继续执行后续代码
    1-2. 验证 options.method
      => 选填
      => 可以不写
      => 只要你写了, 就得是 GET 或者 POST
    1-3. 验证 options.async
      => 选填
      => 可以不写
      => 只要你写了, 就得是 true 或者 false, 必须是一个 布尔值
    1-4. 验证 options.data
      => 选填
      => 可以不写
      => 只要你写了, 就得是一个 字符串 类型
    1-5. 验证 options.headers
      => 选填
      => 可以不写
      => 只要你写了, 就得是一个 对象 数据类型
    1-6. 验证 dataType
      => 选填
      => 可以不写
      => 只要你写了, 就得是一个 'string' 或者 'json'
    1-7. 验证 success
      => 选填
      => 可以不写
      => 只要你写了, 就得是一个 function 类型
*/

/*
  2. 准备一套默认值
    + 对于你没有传递的我设置一套默认值
    + 将来用起来方便
    2-2. 调整一下 url 的内容
      => 判断如果你是 GET 请求, 并且 data 有内容
      => 那么我直接把 data 拼接在 url 的后面
*/
function ajax(options = {}) {
  // options 是一个对象数据类型, 里面是对本次请求的所有配置信息

  // 1. 验证参数内容
  // 1-1. 验证 url 必填
  if (!options.url) {
    // 代码执行到这个, 说明 url 没有
    // 手动抛出异常, 用代码报错
    // 语法: throw new Error('错误信息')
    throw new Error('url 为必填选项 ! ^_^ ')
  }

  // 1-2. 验证 method
  if (!(options.method === undefined || /^(get|post)$/i.test(options.method))) {
    // 代码能执行到这里, 说明你的 method 不是 undefined 也不是 get 也不是 post
    throw new Error('method 目前只接受 POST 和 GET 请求方式 O(∩_∩)O哈哈~')
  }

  // 1-3. 验证 async
  if (!(options.async === undefined || typeof options.async === 'boolean')) {
    // 代码能执行到这里, 说明你的 async 不是 undefined 也不是 布尔值
    throw new Error('async 需要传递一个 布尔值(boolean) 类型 (#^.^#)')
  }

  // 1-4. 验证 data
  if (!(options.data === undefined || typeof options.data === 'string')) {
    throw new Error('data 需要传递一个 字符串(string) 类型 o(*￣︶￣*)o')
  }

  // 1-5. 验证 headers
  if (!(options.headers === undefined || Object.prototype.toString.call(options.headers) === '[object Object]')) {
    // 代码能执行到这里, 说明你的 headers 不是 undefined 也不是对象数据类型
    throw new Error('headers 需要传递一个 对象(Object) 数据类型 o(*￣︶￣*)o')
  }

  // 1-6. 验证 dataType
  if (!(options.dataType === undefined || /^(string|json)$/i.test(options.dataType))) {
    // 代码能执行到这里, 说明你的 dataType 不是 undefined, 也不是 string 也不是 json
    throw new Error('目前 dataType 只接受 "string" 或者 "json" 选项 (*^▽^*)')
  }

  // 1-7. 验证 success
  if (!(options.success === undefined || typeof options.success === 'function')) {
    throw new Error('success 需要传递一个 函数(function) 类型 hh')
  }

  // 2. 准备一套默认值
  const _default = {
    // 默认值没有 url
    url: options.url,
    // 代码能执行到这里, 说明 method 要么是 undefined, 要么是 GET 要么是 POST
    method: options.method || 'GET',
    // 代码能执行到这里, 说明 async 要么是 undefined, 要么是 布尔值
    // async: typeof options.async === 'boolean' ? options.async : true
    // 扩展一个运算符, 空值运算符 ??
    // 只有前面是 空 的时候, 才会用后面
    async: options.async ?? true,
    // 代码能执行到这里, 说明 data 要么是 undefined, 要么是 字符串
    data: options.data || '',
    // 代码能执行到这里, 说明 headers 要么是 undefined, 要么是 对象数据类型
    // 使用 展开运算符把 options.headers 直接展开放进来
    headers: { 'content-type': 'application/x-www-form-urlencoded', ...options.headers },
    // 代码能执行到这里, 说明 dataType 要么是 undefined 要么是 string 要么是 json
    dataType: options.dataType || 'string',
    // 代码能执行到这里, 说明 success 要么是 undefined, 要么是 函数数据类型
    success: options.success || function () {}
  }
  // 2-2. 调整一下 url
  // 正则.test() 检测的方法
  // 如果你写的 字符串 满足正则要求, 就是 true
  // 如果你写的 字符串 不满足正则要求, 就是 false
  // 正则: 从开头到结尾只能有 get 组成, 忽略大小写
  // _default.method: 只能是 get 或者 post
  // /^get$/i.test(_default.method) 为 true 表示您要发送 get 请求
  // _default.data: 只能是 '' 或者 有内容的字符串
  // _default.data 为 true 表示有要携带给后端的参数
  // /^get$/i.test(_default.method) && _default.data 为 true, 表示 是一个 get 请求并且有携带给后端的信息
  // _default.url = _default.url + '?' + _default.data
  if (/^get$/i.test(_default.method) && _default.data) {
    // 代码能执行到这里, 说明是 GET 请求, 并且 data 不是空字符串
    _default.url += '?' + _default.data
  }

  // 3. 发送 ajax
  // 按照 _default 中的内容发送
  const xhr = new XMLHttpRequest()

  xhr.open(_default.method, _default.url, _default.async)

  xhr.onload = function () {
    let res = xhr.responseText

    // 你传递的 dataType 是 'json' 表示你需要执行 JSON.parse() 去解析响应体
    if (_default.dataType === 'json') {
      res = JSON.parse(xhr.responseText)
    }

    // 请求完成了
    // 需要调用回调函数了
    // 函数调用阶段, () 里面是 实参
    _default.success(res)
  }

  // 需要根据 请求方式 来决定是否有请求头
  // 需要根据 是否有 token 决定是否携带 token
  // 直接把 headers 里面的内容设置成请求头
  // 内部有一条信息, 设置一条
  for (let k in _default.headers) {
    // k 分别是, 对象内的每一个 键名
    // _default.headers[k] 分别是, 对象内的每一个 值
    // 设置请求头的时候
    // 语法: xhr.setRequestHeader(名字, 值)
    xhr.setRequestHeader(k, _default.headers[k])
  }

  if (/^get$/i.test(_default.method)) {
    xhr.send()
  } else {
    xhr.send(_default.data)
  }
}

/*
  设置请求头的时候, 可以按照 请求方式去判断
    + 但是我们需要加的请求头一共有两个信息
      => content-type
      => authorization
    + 我们只能携带这两个请求头吗 ?
      => 不一定只有这两个信息
    + content-type 可以不可以设置一个默认值
      => GET 请求, 不需要设置, 因为浏览器会自动设置为 application/x-www-form-urlencoded
      => POST 请求, 我们多数时候, 会设置为 application/x-www-form-urlencoded
      => 可以设置一个默认值
*/


// 使用 promise 的语法对 ajax 进行二次封装
/*
  pAjax({ 本次请求的配置项(url, data, method, headers, dataType, async) })
    .then(res => console.log(res))
*/
function pAjax(options) {
  const p = new Promise((resolve, reject) => {
    ajax({
      url: options.url,
      data: options.data,
      async: options.async,
      method: options.method,
      headers: options.headers,
      dataType: options.dataType,
      success (res) {
        resolve(res)
      }
    })
  })

  return p
}
